//
// Created by chizuru on 2025/9/2.
//

#include "TinyXmlRegisterCLBMap.h"
#include "XMLAuxiliary.h"
using namespace tinyxml2;
using namespace xml_auxiliary;

TinyXmlRegisterCLBMap::TinyXmlRegisterCLBMap(const std::string& xmlPath) {
    XMLError err = doc.LoadFile(xmlPath.c_str());
    if (err != XML_SUCCESS) {
        throw std::runtime_error("TinyXmlRegisterCLBMap: failed to load file: " + xmlPath + " (tinyxml2 error " + std::to_string(err) + ")");
    }

    XMLElement* root = doc.FirstChildElement("Registers");
    if (!root) {
        throw std::runtime_error("TinyXmlRegisterCLBMap: <Registers> root element not found");
    }

    for (XMLElement* reg = root->FirstChildElement("Register"); reg; reg = reg->NextSiblingElement("Register")) {
        const char* categoryAttr = reg->Attribute("category");
        const char* nameAttr = reg->Attribute("name");
        if (!categoryAttr || !nameAttr) {
            // skip malformed entries
            continue;
        }
        std::string category = categoryAttr;
        std::string name = nameAttr;

        // 1) prefer <Address> child text if present
        bool haveAddr = false;
        uint16_t addr = 0;

        XMLElement* addrElem = reg->FirstChildElement("Address");
        if (addrElem && addrElem->GetText()) {
            std::string addrText = trim(std::string(addrElem->GetText()));
            if (parseAddress(addrText, addr)) {
                haveAddr = true;
            }
        }

        // 2) if no Address child, try attribute 'addr'
        if (!haveAddr) {
            const char* attrAddr = reg->Attribute("addr");
            if (attrAddr) {
                std::string addrText = trim(std::string(attrAddr));
                if (parseAddress(addrText, addr)) {
                    haveAddr = true;
                }
            }
        }

        if (!haveAddr) {
            // skip entries without address
            continue;
        }

        // Read optional ToolTip and Description child texts
        std::string tooltip;
        std::string description;

        XMLElement* tipElem = reg->FirstChildElement("ToolTip");
        if (tipElem && tipElem->GetText())
            tooltip = trim(std::string(tipElem->GetText()));

        XMLElement* descElem = reg->FirstChildElement("Description");
        if (descElem && descElem->GetText())
            description = trim(std::string(descElem->GetText()));

        RegInfo info;
        info.address = addr;
        info.tooltip = std::move(tooltip);
        info.description = std::move(description);
        info.category = category;
        info.name = name;

        // compute 64-bit FNV-1a hash of "category.name"
        std::string composite = category + "." + name;
        uint64_t h = fnv1a64(composite);

        map[h].push_back(std::move(info));
    }
}

std::optional<uint16_t> TinyXmlRegisterCLBMap::getAddress(const std::string& category, const std::string& name) const {
    uint64_t h = fnv1a64(category + "." + name);
    auto it = map.find(h);
    if (it == map.end())
        return std::nullopt;
    // scan bucket to disambiguate collisions
    for (const RegInfo& ri : it->second) {
        if (ri.category == category && ri.name == name)
            return ri.address;
    }
    return std::nullopt;
}

std::optional<TinyXmlRegisterCLBMap::RegInfo> TinyXmlRegisterCLBMap::getInfo(const std::string& category, const std::string& name) const {
    uint64_t h = fnv1a64(category + "." + name);
    auto it = map.find(h);
    if (it == map.end())
        return std::nullopt;
    for (const RegInfo& ri : it->second) {
        if (ri.category == category && ri.name == name)
            return ri;
    }
    return std::nullopt;
}

uint64_t TinyXmlRegisterCLBMap::hashKey(const std::string& category, const std::string& name) {
    return fnv1a64(category + "." + name);
}